#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <pthread.h>
#include <stddef.h>
#include <unistd.h>

#include "share_mem.h"
#include "log_entry_write.h"

int32_t s_free_head = -1;
int32_t s_list_head = -1;
int32_t s_list_tail = -1;
clog_node_list_t log_node_list[MAX_LIST_NODES];
static pthread_mutex_t log_list_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t log_list_cond = PTHREAD_COND_INITIALIZER;
char *g_shared_mem = NULL;

int32_t check_log_entry_size(clog_entry_t *entry)
{
    if (entry == NULL) {
        printf("log_entry_write:check_log_entry_size:entry is NULL\n");
        return -1;
    }

    /* check member offset */
    const size_t expected_prio_offset = offsetof(clog_entry_t, prio);
    const size_t expected_pid_offset = offsetof(clog_entry_t, pid);
    const size_t expected_ppid_offset = offsetof(clog_entry_t, ppid);
    const size_t expected_tv_sec_offset = offsetof(clog_entry_t, tv_sec);
    const size_t expected_tv_nsec_offset = offsetof(clog_entry_t, tv_nsec);
    const size_t expected_tag_offset = offsetof(clog_entry_t, tag);
    const size_t expected_message_len_offset = offsetof(clog_entry_t, message_len);
    const size_t expected_message_offset = offsetof(clog_entry_t, message);
    const size_t expected_total_size = sizeof(clog_entry_t);

    const char* entry_base = (const char*)entry;
    const size_t actual_prio_offset = (const char*)&entry->prio - entry_base;
    const size_t actual_pid_offset = (const char*)&entry->pid - entry_base;
    const size_t actual_ppid_offset = (const char*)&entry->ppid - entry_base;
    const size_t actual_tv_sec_offset = (const char*)&entry->tv_sec - entry_base;
    const size_t actual_tv_nsec_offset = (const char*)&entry->tv_nsec - entry_base;
    const size_t actual_tag_offset = (const char*)&entry->tag - entry_base;
    const size_t actual_message_len_offset = (const char*)&entry->message_len - entry_base;
    const size_t actual_message_offset = (const char*)&entry->message - entry_base;
    const size_t actual_total_size = sizeof(*entry);

    if (actual_prio_offset != expected_prio_offset ||
        actual_pid_offset != expected_pid_offset ||
        actual_ppid_offset != expected_ppid_offset ||
        actual_tv_sec_offset != expected_tv_sec_offset ||
        actual_tv_nsec_offset != expected_tv_nsec_offset ||
        actual_tag_offset != expected_tag_offset ||
        actual_message_len_offset != expected_message_len_offset ||
        actual_message_offset != expected_message_offset) {
        printf("clog_entry_t:member offset mismatch\n");
        return -1;
    }

    /* check member size */
    /* check prio size:int32_t */
    if(sizeof(entry->prio) != sizeof(int32_t)) {
        printf("clog_entry_t:member size mismatch\n");
        return -1;
    }
    /* check pid size:int32_t */
    if(sizeof(entry->pid) != sizeof(int32_t)) {
        printf("clog_entry_t:member size mismatch\n");
        return -1;
    }

    /* check ppid size:int32_t */
    if(sizeof(entry->ppid) != sizeof(int32_t)) {
        printf("clog_entry_t:member size mismatch\n");
        return -1;
    }

    /* check tv_sec size:time_t */
    if(sizeof(entry->tv_sec) != sizeof(time_t)) {
        printf("clog_entry_t:member size mismatch\n");
        return -1;
    }

    /* check tv_nsec size:long */
    if(sizeof(entry->tv_nsec) != sizeof(long)) {
        printf("clog_entry_t:member size mismatch\n");
        return -1;
    }

    /* check tag size:CLOG_TAG_SIZE */
    if(sizeof(entry->tag) != CLOG_TAG_SIZE) {
        printf("clog_entry_t:member size mismatch\n");
        return -1;
    }
    /* check message_len size:size_t */
    if(sizeof(entry->message_len) != sizeof(size_t)) {
        printf("clog_entry_t:member size mismatch\n");
        return -1;
    }

    /* check message size:CLOG_BUF_SIZE */
    if(sizeof(entry->message) != CLOG_BUF_SIZE) {
        printf("clog_entry_t:member size mismatch\n");
        return -1;
    }

    /* check total size */
    if(actual_total_size != expected_total_size) {
        printf("clog_entry_t:total size mismatch\n");
        return -1;
    }

    return 0;
} 

static char get_loglevel_str(clog_priority_e prio)
{
    switch (prio) {
        case CLOG_VERBOSE:       return 'V';
        case CLOG_DEBUG:         return 'D';
        case CLOG_INFO:          return 'I';
        case CLOG_WARN:          return 'W';
        case CLOG_ERROR:         return 'E';
        case CLOG_FATAL:         return 'F';

        case CLOG_UNKNOWN:
        default:                        return '?';
    }
}

static void* log_reader_thread(void* arg) {
    clog_entry_t entry;
    int32_t ret;
    char log_buf[CLOG_BUF_SIZE];

    printf("log_reader_thread: started\n");

    while (1) {
        ret = clog_read_entry_from_list(&entry);
        if (ret > 0) {
            shmem_ring_enqueue(g_shared_mem, &entry, sizeof(clog_entry_t));
        } else if (ret != -1) {
            printf("log_reader_thread: read error, ret=%d\n", ret);
        }
    }

    return NULL;
}

int32_t clog_write_device_init(clog_entry_t *entry)
{
    int32_t ret = 0;
    pthread_t reader_thread;

    pthread_mutex_lock(&log_list_mutex);

    ret = check_log_entry_size(entry);
    if (ret < 0) {
        printf("check_log_entry_size failed\n");
        pthread_mutex_unlock(&log_list_mutex);
        return -1;
    }

    s_free_head = (MAX_LIST_NODES > 0) ? 0 : -1;
    for (int32_t i = 0; i < MAX_LIST_NODES; i++) {
        if (i < MAX_LIST_NODES - 1) {
            log_node_list[i].next = i + 1;
        } else {
            log_node_list[i].next = -1;
        }
    }

    s_list_head = -1;
    s_list_tail = -1;

    slave_get_shared_mem(&g_shared_mem, SHM_NAME);
    if (g_shared_mem == NULL) {
        printf("slave_get_shared_mem failed\n");
        pthread_mutex_unlock(&log_list_mutex);
        return -1;
    }

    if (pthread_create(&reader_thread, NULL, log_reader_thread, NULL) != 0) {
        printf("failed to create reader thread\n");
        pthread_mutex_unlock(&log_list_mutex);
        return -1;
    }

    if (pthread_detach(reader_thread) != 0) {
        printf("failed to detach reader thread\n");
        pthread_mutex_unlock(&log_list_mutex);
        return -1;
    }

    printf("log_entry_write:clog_write_device_init\n");
    pthread_mutex_unlock(&log_list_mutex);
    return 0;
}

int32_t clog_write_entry_to_list(const clog_entry_t *entry)
{
    int32_t new_node_idx;

    if (entry == NULL) {
        printf("entry is NULL\n");
        return -1;
    }

    pthread_mutex_lock(&log_list_mutex);

    if (s_free_head < -1 || s_free_head >= MAX_LIST_NODES) {
        printf("invalid s_free_head=%d, resetting free list\n", s_free_head);
        s_free_head = (MAX_LIST_NODES > 0) ? 0 : -1;
        for (int32_t i = 0; i < MAX_LIST_NODES; i++) {
            log_node_list[i].next = (i < MAX_LIST_NODES - 1) ? (i + 1) : -1;
        }
    }

    if (s_free_head == -1) {
        printf("log list is full (max nodes: %d)\n", MAX_LIST_NODES);
        pthread_mutex_unlock(&log_list_mutex);
        return -1;
    }

    new_node_idx = s_free_head;
    if (new_node_idx < 0 || new_node_idx >= MAX_LIST_NODES) {
        printf("invalid free node index: %d\n", new_node_idx);
        pthread_mutex_unlock(&log_list_mutex);
        return -1;
    }

    s_free_head = log_node_list[new_node_idx].next;

    log_node_list[new_node_idx].entry = *entry;
    log_node_list[new_node_idx].next = -1;

    if (s_list_tail == -1) {
        s_list_head = new_node_idx;
        s_list_tail = new_node_idx;
    } else {
        log_node_list[s_list_tail].next = new_node_idx;
        s_list_tail = new_node_idx;
    }

    pthread_cond_signal(&log_list_cond);
    pthread_mutex_unlock(&log_list_mutex);

    return 0;
}

int32_t clog_read_entry_from_list(clog_entry_t *entry)
{
    int32_t ret = 0;
    int32_t node_idx;

    if (entry == NULL) {
        printf("entry is NULL\n");
        return -1;
    }

    pthread_mutex_lock(&log_list_mutex);

    node_idx = s_list_head;
    while (node_idx == -1) {
        pthread_cond_wait(&log_list_cond, &log_list_mutex);
        node_idx = s_list_head;
    }

    if (node_idx < 0 || node_idx >= MAX_LIST_NODES) {
        printf("invalid log node index: %d\n", node_idx);
        pthread_mutex_unlock(&log_list_mutex);
        return -1;
    }

    *entry = log_node_list[node_idx].entry;
    ret = log_node_list[node_idx].entry.message_len;

    s_list_head = log_node_list[node_idx].next;
    log_node_list[node_idx].next = s_free_head;
    s_free_head = node_idx;

    if (s_list_head == -1) {
        s_list_tail = -1;
    }

    pthread_mutex_unlock(&log_list_mutex);

    return ret;
}